类型结构
本包@comate/plugin-schema
提供与插件系统有关的各种共享类型、常量及常用读写能力。
会话通信
由于整体是多进程的体系,不能像普通函数一样进行直接调用,因此制定了一套通信和会话的能力,以满足以下要求:
- 能够使用
Promise
的形态进行一次调用,插件执行完成前,Engine端可以通过简单的await
阻塞自身的逻辑。 - 在一次调用过程中发生的其它调用,例如日志,都会与这一次调用相关联。
社区中普遍使用的RPC封装方案如async-call-rpc都只能解决第一个问题,因此我们自己实现了一套方案。
名词解释
ChannelImplement
:指一个原本已经存在的能够进行通信的对象,这个对象必须有一个message
事件和一个send
方法。从定义可以看出来,一个进程就是典型的ChannelImplement
对象,也可以通过WebSocket
等方式来实现这个接口。Channel
:对ChannelImplement
做一次封装,通过对message
和send
的处理,能够管理会话。其本质是使用sessionId
关联各种message
到同一个会话中。Session
:代表一次会话,可以由Channel#startSession
主动创建,也可以在一条有全新的sessionId
的消息到达时被动创建。所有的发送和接收消息都是在Session
对象中处理的,即发送消息的方法、接收消息的事件监听,都是通过继承Session
类来做的。
┌───────┐ ┌───────┐
│Session│ │Session│
└───────┘ └───────┘
┌────────────────┬─────────┐ ┌─────────┬────────────────┐
│ │ │ │ │ │
│ │ ├────────►│ │ │
│ │ │ │ │ │
│ Engine Process │ Channel │ │ Channel │ Plugin Process │
│ │ │ │ │ │
│ │ │◄────────┤ │ │
│ │ │ │ │ │
└────────────────┴─────────┘ └─────────┴────────────────┘
┌───────┐ ┌───────┐
│Session│ │Session│
└───────┘ └───────┘
自定义会话
正常的使用方法是写2个类,一个继承Session
并定义一系列的事件监听和发送方法,一个继承Channel
并重写createSession
方法返回自己的Session
子类。
对于Session
的子类:
- 定义一个类型
PayloadMap
,它的键是你需要监听的action
常量,值是对应的payload
的类型。 - 定义
class extends Session<PayloadMap>
。 - 重写
initializeListeners
方法,先调用super.initializeListeners()
,再用setListener
方法监听不同的消息action
,TypeScript会自动推导出来payload
类型。 - 如果这个
Session
类是被其它功能使用的,那么添加一系列方法,每个方法是对send
的调用,用来发送指定类型的消息。 - 对处于调用链中间的
Session
实现,你可以使用forwardMessageToParent
方法透传消息到父会话中。
interface GreetingPayload {
name: string;
text: string;
}
interface GoodbyePayload {
name: string;
}
const ACTION_GREETING = 'GREETING';
const ACTION_GOODBYE = 'GOODBYE';
interface PayloadMap {
[ACTION_GREETING]: GreetingPayload;
[ACTION_GOODBYE]: GoodbyePayload;
}
class MySession extends Session<PayloadMap> {
sendGift(price: number) {
this.send({action: 'SEND_GIFT', payload: {price}});
}
protected initializeListeners() {
super.initializeListeners();
this.setListener(
ACTION_GREETING,
payload => console.log(`Greeting from ${payload.name}: ${payload.text}`)
);
this.setListener(
ACTION_GOODBYE,
payload => console.log(`${payload.name} leaves`)
);
}
}
随后,实现自己的Channel
类型,继承时通过泛型指定自己的Session
子类,只需要createSession
方法即可:
class MyChannel extends Channel<MySession> {
protected createSession(init: SessionInit) {
return new MySession(init, this.implement);
}
}
由于Session
只能由Channel
创建,因此如果你的Session
实现需要很多其它的依赖,就要先在Channel
构造函数中获取,再通过createSession
传给Session
子类。
调用会话
作为主动调用方,使用Channel#startSession
可以启动一个会话并发送一个消息过去,这个方法接收一个sessionId
字符串(使用UUID即可)或者一个父的Session
对象,会返回Promise
直到会话结束(收到SESSION_FINISH
消息)。
如果调用startSession
时传的是一个父Session
对象,那么2个会话就会建立父子关系,部分特殊的消息会由子向父的透传。
在调用startSession
后,指定的消息被发送到接收端(例如子进程的Channel
),在收到第一条有全新的sessionId
时,Channel
会创建一个Session
对象并处理这条消息(对应setListener
监听的回调函数)。在处理中可以用send
、log
等方法发消息回到调用子(如主进程的Channel
)。
所有通过Session
对象的log
和send
发送的消息,都会带上对应的sessionId
,以便将所有的信息关联起来。
对于特殊的内置消息类型(当前仅LOG
),它们默认会向父会话透传,即子会话的日志最终只在顶层处理,中间层不管理日志。